package com.fs.service;


import com.alibaba.fastjson.JSON;
import com.fs.global.config.WxPayConfig;
import com.fs.service.bo.wxpay.BoWxPayParam;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author : ppx
 * @date : 10:10 2021/3/12
 */
@Service
@Log4j2
@Validated
public class WxPayService {

    private final WxPayConfig payConfig;

    public WxPayService(WxPayConfig payConfig) {
        this.payConfig = payConfig;
    }

    /**
     * 统一下单
     */
    public Map<String, String> requestPrepayId(@NotNull @Valid BoWxPayParam boWxPay) throws RequestWxApiException {

        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
        String bodyStr = buildApiParam(boWxPay);
        StringEntity entity;
        try {
            entity = new StringEntity(bodyStr);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            throw new RequestWxApiException("构造预支付id请求参数异常");
        }
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-Type", "application/json;charset=utf-8");
        httpPost.setEntity(entity);

        try (CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
                .withMerchant(payConfig.getMchId(), payConfig.getMchSerialNo(), payConfig.getPrivateKey())
                .withValidator(new WechatPay2Validator(payConfig.getVerifier())).build();

             CloseableHttpResponse response = httpClient.execute(httpPost)) {

            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                log.info("请求微信预支付id成功");
                String prepayId = JSON.parseObject(EntityUtils.toString(response.getEntity())).getString("prepay_id");
                Map<String, String> result = buildWxPayObject(payConfig.getPrivateKey(), prepayId);
                if (null == result) {
                    throw new RequestWxApiException("签名算法异常");
                }
                return result;
            } else {
                log.error("failed,resp code = " + statusCode + ",return body = " + EntityUtils.toString(response.getEntity()));
                throw new RequestWxApiException(statusCode);
            }
        } catch (IOException e) {
            e.printStackTrace();
            throw new RequestWxApiException("预支付id请求异常");
        }
    }

    /**
     * 关闭订单
     */
    public void closePurchase(String out_trade_no) throws RequestWxApiException {
        String template = "https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/%s/close";
        String url = String.format(template, out_trade_no);
        HttpPost httpPost = new HttpPost(url);
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-Type", "application/json;charset=utf-8");

        String bodyStr = "{\"mchid\": \"" + payConfig.getMchId() + "\"}";
        StringEntity entity = null;
        try {
            entity = new StringEntity(bodyStr);
        } catch (UnsupportedEncodingException e) {
            throw new RequestWxApiException("关闭微信支付订单失败");
        }
        httpPost.setEntity(entity);

        try (CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
                .withMerchant(payConfig.getMchId(), payConfig.getMchSerialNo(), payConfig.getPrivateKey())
                .withValidator(new WechatPay2Validator(payConfig.getVerifier())).build();

             CloseableHttpResponse response = httpClient.execute(httpPost)){

            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode != 204) {
                throw new RequestWxApiException(statusCode);
            }


        }catch (IOException e){
            throw new RequestWxApiException("关闭微信支付订单失败");
        }

    }

    private String buildApiParam(BoWxPayParam bo) {
        LocalDateTime expiredTime = LocalDateTime.now().plusSeconds(60);
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss+08:00");
        String formatTime = expiredTime.format(formatter);
        ApiParam param = new ApiParam().setAppid(payConfig.getAppId()).setMchid(payConfig.getMchId()).setDescription(bo.getDescription())
                .setOut_trade_no(bo.getOutTradeNo()).setNotify_url(payConfig.getNotifyUrl()).setTime_expire(formatTime);

        Map<String, Integer> amount = new HashMap<>();
        amount.put("total", (int) (bo.getMoney() * 100));
        param.setAmount(amount);

        Map<String, String> payer = new HashMap<>();
        payer.put("openid", bo.getOpenId());
        param.setPayer(payer);


        return JSON.toJSONString(param);
    }

    @Nullable
    private Map<String, String> buildWxPayObject(PrivateKey key, String prepayId) {
        String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
        String nonceStr = buildNonceStr();
        String packageStr = "prepay_id=" + prepayId;

        String signMsg = Stream.of(payConfig.getAppId(), timestamp, nonceStr, packageStr)
                .collect(Collectors.joining("\n", "", "\n"));

        Map<String, String> result = new HashMap<>();
        result.put("appId", payConfig.getAppId());
        result.put("timeStamp", timestamp);
        result.put("nonceStr", nonceStr);
        result.put("package", packageStr);
        result.put("signType", "RSA");

        try {
            String signStr = new String(Base64.encodeBase64(sign256(signMsg, key)));
            result.put("paySign", signStr);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return result;
    }

    public byte[] sign256(String data, PrivateKey privateKey) throws NoSuchAlgorithmException, InvalidKeyException,
            SignatureException {
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(data.getBytes(StandardCharsets.UTF_8));
        return signature.sign();
    }

    private String buildNonceStr() {
        String base   = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        Random random   = new Random();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 32; i++) {
            int number = random.nextInt(base.length());
            sb.append(base.charAt(number));
        }
        return sb.toString();
    }


    @Data
    @Accessors(chain = true)
    private static class ApiParam {
        private String appid;
        private String mchid;
        private String description;
        private String out_trade_no;
        private String notify_url;
        private Map<String, Integer> amount;
        private Map<String, String> payer;
        private String time_expire;
    }

    public static class RequestWxApiException extends Exception {
        RequestWxApiException(int statusCode) {
            super("调用微信api失败,code=" + statusCode);
        }

        RequestWxApiException(String err) {
            super(err);
        }
    }
}
